﻿/**********************************************************************
 * Copyright (c) 2008, j. montgomery                                  *
 * All rights reserved.                                               *
 *                                                                    *
 * Redistribution and use in source and binary forms, with or without *
 * modification, are permitted provided that the following conditions *
 * are met:                                                           *
 *                                                                    *
 * + Redistributions of source code must retain the above copyright   *
 *   notice, this list of conditions and the following disclaimer.    *
 *                                                                    *
 * + Redistributions in binary form must reproduce the above copyright*
 *   notice, this list of conditions and the following disclaimer     *
 *   in the documentation and/or other materials provided with the    *
 *   distribution.                                                    *
 *                                                                    *
 * + Neither the name of j. montgomery's employer nor the names of    *
 *   its contributors may be used to endorse or promote products      *
 *   derived from this software without specific prior written        *
 *   permission.                                                      *
 *                                                                    *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS*
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT  *
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS  *
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE     *
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,*
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES           *
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR *
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) *
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,*
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)      *
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED*
 * OF THE POSSIBILITY OF SUCH DAMAGE.                                 *
 **********************************************************************/
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.IO;
using System.Linq;
using System.Security.Principal;
using System.Security.AccessControl;
using System.ServiceProcess;
using System.Xml;

using UpdateDnsOMatic.Enums;
using UpdateDnsOMatic.Utility;
using System.Runtime.InteropServices;

namespace UpdateDnsOMatic.Installer
{
    [RunInstaller(true)]
    public partial class ProjectInstaller : System.Configuration.Install.Installer
    {
        public ProjectInstaller()
        {
            InitializeComponent();

            if (IsNetworkServiceAvailable())
            {
                serviceProcessInstaller.Account = System.ServiceProcess.ServiceAccount.NetworkService;
            }
            else
            {
                serviceProcessInstaller.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
            }

            dnsOMaticServiceInstaller.FailureActions.Add(
              new FailureAction(RecoverAction.Restart, 10000));
            dnsOMaticServiceInstaller.FailureActions.Add(
              new FailureAction(RecoverAction.Restart, 10000));
            dnsOMaticServiceInstaller.FailureActions.Add(
              new FailureAction(RecoverAction.Restart, 10000));

            dnsOMaticServiceInstaller.StartOnInstall = true;
        }

        public override void Install(System.Collections.IDictionary stateSaver)
        {
            base.Install(stateSaver);
        }

        public override void Uninstall(IDictionary savedState)
        {
            base.Uninstall(savedState);
        }

        private void StageConfigFile()
        {
            Context.LogMessage("Staging Config File");
            string username = Context.Parameters["UserName"];
            string password = Context.Parameters["Password"];
            
            UpdateDnsOMatic.Utility.CryptoUtility crypto = new UpdateDnsOMatic.Utility.CryptoUtility();
            string cipherText = crypto.EncryptData(password);
            
            // Get the path to the executable file that is being installed on the target computer
            string assemblypath = Context.Parameters["assemblypath"];
            string appConfigPath = assemblypath + ".config";
            
            // Write the path to the app.config file
            XmlDocument doc = new XmlDocument();
            doc.Load(appConfigPath);

            XmlNode node = doc.SelectSingleNode("/configuration/DnsOMaticSection");
            if (node != null)
            {
                node.Attributes["UserName"].Value = username;
                node.Attributes["Password"].Value = cipherText;
                doc.Save(appConfigPath);
            }
            Context.LogMessage("Completed Staging Configuration File.");
        }

        private void ConfigureAcls()
        {
            Context.LogMessage("Begin configuring custom ACLS's.");
            string assemblypath = Context.Parameters["assemblypath"];
            string appConfigPath = assemblypath + ".config";

            // Get info on the file in order to extract the directory
            FileInfo fileInfoAssemblyPath = new FileInfo(assemblypath);

            // Extract the directory Access Control
            DirectorySecurity directorySecurity = fileInfoAssemblyPath.Directory.GetAccessControl();
            FileSecurity configFileSecurity = File.GetAccessControl(appConfigPath);
            FileSecurity assemblyFileSecurity = File.GetAccessControl(assemblypath);

            // Create a security Identifier for NETWORK SERVICE account
            SecurityIdentifier si = null;
            // Make sure we can use the Network Service account by
            // Check OS version, NetworkService account was introduced
            // after Win2k (so XP, 2k3 and Vista have it)
            if (IsNetworkServiceAvailable())
            {
                // WinXP, Win2k3, Vista - we run as NetworkService
                si = new SecurityIdentifier(WellKnownSidType.NetworkServiceSid, null);
            }
            else
            {
                // Win2k or unable to detect, run as LocalSystem
                si = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null);
            }
            
            // Create a new ACE 
            FileSystemAccessRule ace = new FileSystemAccessRule(
                si,
                FileSystemRights.ReadAndExecute |
                FileSystemRights.CreateFiles,
                AccessControlType.Allow);


            // Additionally, need Modify permissions to the .config file
            FileSystemAccessRule aceConfigFile = new FileSystemAccessRule(
                si,
                FileSystemRights.ReadAndExecute |
                FileSystemRights.Write |
                FileSystemRights.Modify,
                AccessControlType.Allow);


            // Add ACE to Assembly so the SERVICE account CAN READ/Execute the assembly
            assemblyFileSecurity.AddAccessRule(ace);
            File.SetAccessControl(assemblypath, assemblyFileSecurity);
            // Add ACE to Config File so SERVICE account can READ the configuration
            configFileSecurity.AddAccessRule(aceConfigFile);
            File.SetAccessControl(appConfigPath, configFileSecurity);

            // Add the specified access control list (ACL) permission to the directory.
            directorySecurity.AddAccessRule(ace);
            // Persist the specified access control list (ACL) permission for the directory
            fileInfoAssemblyPath.Directory.SetAccessControl(directorySecurity);
            Context.LogMessage("Done configuring ACLS's.");
        }

        private void serviceProcessInstaller_AfterInstall(object sender, InstallEventArgs e)
        {
            Context.LogMessage("AfterInstall event fired.");
            // Set ACL's so SERVICE account can read/execute/write to Application Folder
            this.ConfigureAcls();

            // Update config file with Setup Configured Parameters
            this.StageConfigFile();
        }

        public override void Commit(IDictionary savedState)
        {
            base.Commit(savedState);

            // Setup SERVICE DACLS
            this.SetServiceDAcls(dnsOMaticServiceInstaller.ServiceName);
        }

        private bool IsNetworkServiceAvailable()
        {
            // Check for Windows Platfor, Win32NT, greater then Version 5.1.
            // to use NetworkService account. If 5.0
            bool useNetworkService = true;
            
            // NT Platform? (not windows 95, 98, ME
            if (Environment.OSVersion.Platform == PlatformID.Win32NT)
            {
                if (Environment.OSVersion.Version.Major == 4)
                {
                    // Version 4.0 (WinNT) - no way
                    useNetworkService = false;
                }
                if (Environment.OSVersion.Version.Major == 5)
                {
                    // Major Version == 5, Win2k, WinXp, Win2k3
                    if (Environment.OSVersion.Version.Minor > 0)
                    {
                        // Minor Version = 1 or 2, WinXP. win2k3,
                        useNetworkService = true;
                    }
                    else
                    {
                        // Minor Version = 0, Win2k
                        useNetworkService = false;
                    }
                }
                else if (Environment.OSVersion.Version.Major == 6)
                {
                    // Version numbers between Vista and Server 2008 are the same (including Service Packs)
                    useNetworkService = true;
                    // TODO - Detect minor versions as new versions come out.
                }
                else if (Environment.OSVersion.Version.Major == 7)
                {
                    useNetworkService = true;
                }
                else
                {
                    // Unknown Version
                }
            }
            else
            {
                useNetworkService = false;
            }

            return useNetworkService;
        }


        public void SetServiceDAcls(string serviceName)
        {
            ServiceController sc = new ServiceController(serviceName);
            byte[] psd = new byte[0];
            uint bufSizeNeeded;
            bool ok = false;

            try
            {
                ok = AdvApi32.QueryServiceObjectSecurity(sc.ServiceHandle, SecurityInfos.DiscretionaryAcl, psd, 0, out bufSizeNeeded);
            }
            catch (Exception)
            {
                // QueryServiceObjectSecurtiy failed.
                ok = false;
                bufSizeNeeded = 0;
            }

            if (!ok)
            {
                int err = Marshal.GetLastWin32Error();
                if (err == 0x0000007A) // ERROR_INSUFFICIENT_BUFFER
                {
                    // expected; now we know bufsize
                    psd = new byte[bufSizeNeeded];
                    ok = AdvApi32.QueryServiceObjectSecurity(sc.ServiceHandle, SecurityInfos.DiscretionaryAcl, psd, bufSizeNeeded, out bufSizeNeeded);
                }
                else
                {
                    throw new ApplicationException("error calling QueryServiceObjectSecurity() to get DACL for " + serviceName + ": error code=" + err);
                }
            }
            if (!ok)
                throw new ApplicationException("error calling QueryServiceObjectSecurity(2) to get DACL for " + serviceName + ": error code=" + Marshal.GetLastWin32Error());

            // get security descriptor via raw into DACL form so ACE
            // ordering checks are done for us.
            RawSecurityDescriptor rsd = new RawSecurityDescriptor(psd, 0);
            RawAcl racl = rsd.DiscretionaryAcl;
            DiscretionaryAcl dacl = new DiscretionaryAcl(false, false, racl);

            SecurityIdentifier interactiveUserSID = new SecurityIdentifier(WellKnownSidType.InteractiveSid, null);

            // Loop over each access control list in the Disrectionary Access Control List.
            foreach (CommonAce ace in dacl)
            {
                if (ace.SecurityIdentifier.IsWellKnown(WellKnownSidType.LocalSystemSid))
                {
                    Context.LogMessage("SID: " + WellKnownSidType.LocalSystemSid);
                    // LOCAL SYSTEM sid should have ALL ACCESS
                    dacl.SetAccess(AccessControlType.Allow, ace.SecurityIdentifier, (int)ServiceRights.AllAccess, InheritanceFlags.None, PropagationFlags.None);
                }
                else if (ace.SecurityIdentifier.IsWellKnown(WellKnownSidType.InteractiveSid))
                {
                    Context.LogMessage("SID: " + WellKnownSidType.InteractiveSid);
                    // INTERACTIVE SID should only be able to READ Service, not stop, start, restart, pause
                    dacl.SetAccess(AccessControlType.Allow, ace.SecurityIdentifier, (int)ServiceRights.GenericRead, InheritanceFlags.None, PropagationFlags.None);
                }
                else if (ace.SecurityIdentifier.IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid))
                {
                    Context.LogMessage("SID: " + WellKnownSidType.BuiltinAdministratorsSid);
                    // LOCAL SYSTEM sid should have ALL ACCESS
                    dacl.SetAccess(AccessControlType.Allow, ace.SecurityIdentifier, (int)ServiceRights.AllAccess, InheritanceFlags.None, PropagationFlags.None);
                }
                else
                {
                    // Purge all other DACLS
                    dacl.Purge(ace.SecurityIdentifier);
                }

                Context.LogMessage("ACE SecurityIdentifier: " + ace.SecurityIdentifier.Value);
                Context.LogMessage("ACE Type: " + ace.AceType);
                Context.LogMessage("ACE AccessMask SERVICE: " + (ServiceRights)ace.AccessMask);

                Context.LogMessage("ACE Flags: " + ace.AceFlags);
                Context.LogMessage(Environment.NewLine);
            }

            // convert discretionary ACL back to raw form; looks like via byte[] is only way
            byte[] rawdacl = new byte[dacl.BinaryLength];
            dacl.GetBinaryForm(rawdacl, 0);
            rsd.DiscretionaryAcl = new RawAcl(rawdacl, 0);

            // set raw security descriptor on service again
            byte[] rawsd = new byte[rsd.BinaryLength];
            rsd.GetBinaryForm(rawsd, 0);
            ok = AdvApi32.SetServiceObjectSecurity(sc.ServiceHandle, SecurityInfos.DiscretionaryAcl, rawsd);
            if (!ok)
            {
                throw new ApplicationException("error calling SetServiceObjectSecurity(); error code=" + Marshal.GetLastWin32Error());
            }

        }
    }
}
